package monash.med.crystallography.DatasetTools.UnTarBzipper;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.apache.tools.bzip2.CBZip2InputStream;
import org.apache.tools.tar.TarEntry;
import org.apache.tools.tar.TarInputStream;

/**
 * UnTarBzipper by Steve Androulakis - Decompresses a bzip compressed tar archive and extracts the archive files to a directory
 * <p>
 * Use static method UnTarBzip(String sourceDir[, String destDir])
 * 
 * @author Steve Androulakis (Monash University)
 * @version 1.0
*/	
public class UnTarBzipper
{
	
	private static String bzipName;
	private static String tarName;
	private static String destDir;
	
	// automatically creates destination directory next to tar.bz file
	/**
	 * @param  bzipName The path to the tar.bz file to be decompressed/extracted
	 */		
	public static String UnTarBzip(String bzipName)
	{
		File destDirF = new File(new File(bzipName).getAbsolutePath() + ".unarchived" + File.separator);
		System.out.println(destDirF.getAbsolutePath());
		destDirF.mkdirs();
		UnTarBzipper.destDir = destDirF.getAbsolutePath();
		
		UnTarBzipper.bzipName = bzipName;
		UnTarBzipper.tarName = generateTarName(bzipName);
		
		unBzip();
		new File(unTar()).delete();
		
		System.out.println("Completed process. Files saved to: " + UnTarBzipper.destDir);
		
		return UnTarBzipper.destDir;
	}
	
	// takes destination directory
	/** 
	 * @param  bzipName The path to the tar.bz file to be decompressed/extracted
	 * @param  destDir The destination path for the extracted files
	 */	
	public static String UnTarBzip(String bzipName, String destDir)
	{
		File destDirF = new File(new File(destDir).getAbsolutePath() + File.separator +
                        new File(bzipName).getName() + ".unarchived" + File.separator);
		destDirF.mkdirs();
		UnTarBzipper.destDir = destDirF.getAbsolutePath();

		UnTarBzipper.bzipName = bzipName;
		UnTarBzipper.tarName = generateTarName(bzipName);
		
		unBzip();
		new File(unTar()).delete();
		
		return UnTarBzipper.destDir;
	}	

	private static String unBzip()
	{
		FileInputStream readTwoBytes;
		try
		{
			readTwoBytes = new FileInputStream(bzipName);
			readTwoBytes.read(); // to get rid of the 'BZ' characters at the
			// start of a bzip2 file
			readTwoBytes.read();
			System.out.println("Decompressing bzip2 archive...");
			final FileOutputStream output = new FileOutputStream(tarName);
			final CBZip2InputStream input = new CBZip2InputStream(readTwoBytes);
			copy(input, output);
			input.close();
			output.close();
			
		}
		catch (FileNotFoundException e)
		{
			System.out.println(bzipName + " not found for decompression. Exiting...");
		}
		catch (IOException e)
		{
			System.out.println("ERROR: Failed or interrupted file operation");
		}
		catch (Exception e)
		{
			System.out.println(e.getMessage());
		}
		return bzipName;
	}

	private static void copy(CBZip2InputStream input, FileOutputStream output) throws IOException
	{
		final byte[] buffer = new byte[8024];
		int n = 0;
		while (-1 != (n = input.read(buffer)))
		{
			output.write(buffer, 0, n);
		}
	}

	private static String unTar()
	{
		try
		{
			FileInputStream in = new FileInputStream(new File(tarName));
			System.out.println("Reading Tar File...");
			TarInputStream tin = new TarInputStream(in);
			TarEntry tarEntry = tin.getNextEntry();
			if (new File(destDir).exists())
			{
				while (tarEntry != null)
				{
										
					File destPath = new File(destDir + File.separatorChar + tarEntry.getName());
					System.out.println("Processing " + destPath.getAbsoluteFile());
					if (!tarEntry.isDirectory())
					{
						// start of new code
						String pathStr = destPath.getPath();
						int idx = pathStr.lastIndexOf(File.separatorChar);
						if (idx > 0)
						{
							File destDir = new File(pathStr.substring(0, idx));
							destDir.mkdirs();
						}
						// end of new code
						FileOutputStream fout = new FileOutputStream(destPath);
						tin.copyEntryContents(fout);
						fout.close();
					}
					else
					{
						destPath.mkdir();
					}
					tarEntry = tin.getNextEntry();
				}
				tin.close();
				System.out.println("Finished Extracting To " + destDir);
			}
			else
			{
				throw new Exception("That destination directory doesn't exist! " + destDir);
			}
		}
		catch (IOException e)
		{
			System.out.println(tarName + " not found for extraction. Exiting...");
		}
		catch (Exception e)
		{
			System.out.println(e.getMessage());
		}

		return tarName;
	}

	private static String generateTarName(String bzipName)
	{
		int idx = bzipName.lastIndexOf(".");
		return bzipName.substring(0, idx);
	}

	/**
	 * The commandline implementaton of the {@link UnTarBzipper} class.
	 * <p>
	 * Run <i>java -jar UnTarBzipper.jar</i> for instructions on usage.
	 */
	public static void main(final String[] args) throws Exception
	{
		if (args.length == 1)
		{
			UnTarBzipper.UnTarBzip(args[0]);
		}
		else if(args.length == 2)
		{
			UnTarBzipper.UnTarBzip(args[0], args[1]);
		}
		else
		{
			System.out
					.println("UnTarBzipper by Steve Androulakis - Decompresses a bzip compressed tar archive and extracts the archive" +
							" files to a directory");
			System.out.println("");
			System.out.println("USAGE:");
			System.out.println("UnTarBzipper <bzipName> <destinationDir>");
			System.out.println("");
			System.out.println("bzipName: The path to the tar.bz file to be decompressed/extracted");
			System.out.println("destinationDir (optional): The destination path for the extracted files");
			System.out.println("    If not specified, files will be placed alongside the original archive");
		}
	}
}